﻿using System;
using System.Collections.Generic;
using System.Threading;

using Heaven.Graphics;
using Heaven.Engine.Concurrency;
using Heaven.Mathematics;


namespace Heaven.Engine
{
    /// <summary>
    /// Renderer is a core object. It is responsible 
    /// for end-rendering of the content
    /// </summary>
    public class Renderer : CoreRenderer
    {   
        #region Fields

        // Graphics device
        WeakReference deviceReference;
        // Indicates that the current content has been invalidated
        AutoResetEvent invalidatedEvent = new AutoResetEvent(true);
        // Require that renderer to be shuted down
        AutoResetEvent shutdownEvent = new AutoResetEvent(false);
        // Indicates that renderer has been shuted down
        ManualResetEvent shutdownCompleteEvent = new ManualResetEvent(false);
        // Indicates that renderer can process
        ManualResetEvent canProcessEvent = new ManualResetEvent(true);
        // Indicates that renderer has been finished rendering
        ManualResetEvent finishedProcessEvent = new ManualResetEvent(true);

        #endregion

             
        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        public Renderer()
        {
            
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">Render target</param>
        /// <param name="source">Source nodes</param>
        /// <param name="camera">Camera</param>
        public Renderer(Node source, Camera camera) : base(source, camera)
        {
           
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">Render target</param>
        /// <param name="source">Source nodes</param>
        /// <param name="camera">Camera</param>
        public Renderer(RenderTarget target, Node source, Camera camera) : base(target, source, camera)
        {
        }
               

        

        /// <summary>
        /// Destructor
        /// </summary>
        ~Renderer() { shutdownEvent.Set(); }

        #endregion
                
        #region Properties

        #endregion

        #region Methods

        /// <summary>
        /// Starts rendering process
        /// </summary>
        /// <param name="device">Graphics Device</param>
        public void Start(IDevice device)
        {
            this.deviceReference = new WeakReference(device);

            // Initialize render-thread
            Thread thread = new Thread(Async);
            thread.Priority = ThreadPriority.Normal;
            thread.Name = "Heaven Asynchronous Rendering";
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true; // FIXME:
            thread.Start();

            Invalidated += new EventHandler(OnRendererInvalidated);
        }

        /// <summary>
        /// Shutdowns rendering process
        /// </summary>
        public void Shutdown()
        {            
            GC.SuppressFinalize(this);
            shutdownEvent.Set();
            canProcessEvent.Set();
            shutdownCompleteEvent.WaitOne();            
        }

        /// <summary>
        /// The method ends when rendering finishes
        /// </summary>
        public void Redraw()
        {
            finishedProcessEvent.Reset();
            invalidatedEvent.Set();
            finishedProcessEvent.WaitOne();
        }

        #endregion

        #region Asynchronous update

        // Asynchronous update
        void Async()
        {
            WaitHandle[] invalidateAndShutdown = new WaitHandle[] { invalidatedEvent, shutdownEvent };
            IDevice device = null;

            do
            {
                finishedProcessEvent.Reset();
                device = (IDevice)deviceReference.Target;
                if (device == null) return;

                device.Begin();
                Render(device);
                device.Present();
                device = null;
                finishedProcessEvent.Set();
                
                // Can we render the next frame?
                canProcessEvent.WaitOne();

                if (WaitHandle.WaitAny(invalidateAndShutdown) == 1)
                {
                    // If shutdown event is fired
                    shutdownCompleteEvent.Set();
                    return;
                }
            }
            while (true);
        }
               
        void OnRendererInvalidated(object sender, EventArgs e)
        {
            invalidatedEvent.Set();
        }

        #endregion
               
    }

    /*
    /// <summary>
    /// Renderer is a core object. It is responsible 
    /// for end-rendering of the content
    /// </summary>
    public class OldRenderer : GenericCollection<RenderQueue>
    {
        #region Events


        #endregion

        #region Fields

        // Shadow renderer
        ShadowRenderer shadowRenderer = null;

        #endregion

        #region Properties

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="device">Graphics device</param>
        public Renderer(IDevice device)
        {
            
            this.shadowRenderer = new ShadowRenderer(device);

//            GlobalInvalidated += delegate { invalidatedEvent.Set(); };
        }
               

        #endregion

        #region Methods

        /// <summary>
        /// Invalidates current content
        /// </summary>
        public void Invalidate()
        {
            shadowRenderer.Invalidate();   
        }

        /// <summary>
        /// Adds an item
        /// </summary>
        /// <param name="renderQueue">Render queue</param>
        public override void Add(RenderQueue renderQueue)
        {
            base.Add(renderQueue);
            shadowRenderer.Add(renderQueue);
            renderQueue.Invalidated += new EventHandler(OnRenderQueueInvalidated);
        }

        void OnRenderQueueInvalidated(object sender, EventArgs e)
        {
            shadowRenderer.Invalidate();
        }

        /// <summary>
        /// Removes the given item
        /// </summary>
        /// <param name="renderQueue">Render queue</param>
        public override void Remove(RenderQueue renderQueue)
        {
            renderQueue.Invalidated -= new EventHandler(OnRenderQueueInvalidated);
            base.Remove(renderQueue);
            shadowRenderer.Remove(renderQueue);
        }

        /// <summary>
        /// Shutdowns rendering process
        /// </summary>
        public void Shutdown()
        {
            shadowRenderer.Shutdown();
        }

        #endregion
    }

    /// <summary>
    /// Shadow renderer is async part of renderer
    /// </summary>
    class ShadowRenderer : DeferredCollection<RenderQueue>
    {
        #region Fields

        // Graphics device
        WeakReference deviceReference;
        // Indicates that the current content has been invalidated
        AutoResetEvent invalidatedEvent = new AutoResetEvent(true);
        // Require that renderer to be shuted down
        AutoResetEvent shutdownEvent = new AutoResetEvent(false);
        // Indicates that renderer has been shuted down
        ManualResetEvent shutdownCompleteEvent = new ManualResetEvent(false);
        // Indicates that renderer can process
        ManualResetEvent canProcessEvent = new ManualResetEvent(true);
        // Indicates that renderer has been finished rendering
        ManualResetEvent finishedProcessEvent = new ManualResetEvent(true);

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="device">Graphics device</param>
        public ShadowRenderer(IDevice device)
        {
            this.deviceReference = new WeakReference(device);
            device.AvailabilityChanged += new EventHandler(OnDeviceAvailabilityChanged);

            // Initialize render-thread
            Thread thread = new Thread(Async);
            thread.Priority = ThreadPriority.Normal;
            thread.Name = "Asynchronous rendering";
            thread.IsBackground = true; // FIXME:
            thread.Start();

            Invalidated += new EventHandler(OnShadowRendererInvalidated);
        }

        void OnDeviceAvailabilityChanged(object sender, EventArgs e)
        {
            IDevice device = (IDevice)deviceReference.Target;
            if (device == null) return;
            if (device.IsAvailable) canProcessEvent.Set();
            else canProcessEvent.Reset();
        }

        void OnShadowRendererInvalidated(object sender, EventArgs e)
        {
            invalidatedEvent.Set();
        }

        /// <summary>
        /// Destructor
        /// </summary>
        ~ShadowRenderer() { shutdownEvent.Set(); }

        #endregion
                
        #region Properties

        #endregion

        #region Methods

        /// <summary>
        /// Shutdowns rendering process
        /// </summary>
        public void Shutdown()
        {            
            GC.SuppressFinalize(this);
            shutdownEvent.Set();
            canProcessEvent.Set();
            shutdownCompleteEvent.WaitOne();            
        }

        /// <summary>
        /// Methods end when rendering has finished
        /// </summary>
        public void Redraw()
        {
            finishedProcessEvent.Reset();
            invalidatedEvent.Set();
            finishedProcessEvent.WaitOne();
        }

        #endregion

        #region Asynchronous update

        // Asynchronous update
        void Async()
        {
            WaitHandle[] invalidateAndShutdown = new WaitHandle[] { invalidatedEvent, shutdownEvent };
            IDevice device = null;

            do
            {
                try
                {
                    if (IsChanged)
                    {
                        // Perform deferred actions 
                        // under the collection
                        Perform();
                    }

                    finishedProcessEvent.Reset();
                    device = (IDevice)deviceReference.Target;
                    if (device == null) return;
                    Render(device);
                    device = null;
                    finishedProcessEvent.Set();
                }
                catch (Exception exception) 
                { 
                    // FIXME: add exception handler
                    //return; 
                }
                
                // Is device available?
                canProcessEvent.WaitOne();

                if (WaitHandle.WaitAny(invalidateAndShutdown) == 1)
                {
                    // If shutdown event is fired
                    shutdownCompleteEvent.Set();
                    return;
                }
            }
            while (true);
        }

        void Render(IDevice device)
        {
            try
            {
                device.Begin();
                foreach (RenderQueue queue in this)
                {
                    foreach (RenderGroup group in queue.Shadow)
                    {
                        group.Draw(device);
                    }
                }
            }
            catch (Exception exception) { System.Diagnostics.Debug.WriteLine("Renderer error: " + exception.Message + " \nStack: \n" + exception.StackTrace); throw; }
            finally
            {
                device.Present();
            }
        }

        #endregion
    }*/
}
